#include "Autowall.h"

Autowall g_pAutowall;

void Autowall::Init()
{
	BYTE lpLookup[]       = {0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x08, 0x83, 0xEC, 0x08, 0x85, 0xC0, 0x74, 0x18};
	BYTE lpWeaponHandle[] = {0x55, 0x8B, 0xEC, 0x66, 0x8B, 0x45, 0x08, 0x66, 0x3B, 0x05};
	BYTE lpWeaponAlias[]  = {0x55, 0x8B, 0xEC, 0x8B, 0x4D, 0x08, 0x33, 0xC0, 0xEB, 0x06, 0x8D, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x39, 0x0C, 0xC5, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0A, 0x40, 0x83, 0xF8, 0x26};
	BYTE lpBulletParams[] = {0x55, 0x8B, 0xEC, 0x56, 0x8B, 0x75, 0x08, 0x68, 0x00, 0x00, 0x00, 0x00, 0x56, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x08, 0x84, 0xC0};

	DWORD dwClient = (DWORD)GetModuleHandleA("client.dll");

	dwLookupWeaponInfoSlot			= FindPattern::Find(dwClient, 0x899FFC, lpLookup,	    "xxxxxxxxxxxxx");
	dwGetFileWeaponInfoFromHandle	= FindPattern::Find(dwClient, 0x899FFC, lpWeaponHandle, "xxxxxxxxxx");
	dwGetWeaponAliasById			= FindPattern::Find(dwClient, 0x899FFC, lpWeaponAlias,  "xxxxxxxxxxxxxxxxxxx????xxxxxx");
	dwGetBulletTypeParameters		= FindPattern::Find(dwClient, 0x899FFC, lpBulletParams, "xxxxxxxx????xx????xxxxx");
}

bool Autowall::GetWeaponInfo(WeaponInfo_t* weaponInfo, CBaseCombatWeapon* weapon)
{
	m_bIsWeaponInfoValid = false;
	
	int id = weapon->GetWeaponId();

	if(!id)
		return false;

	typedef char* (__cdecl* tGetWeaponAliasById)(int id);
	tGetWeaponAliasById GetWeaponAliasById = (tGetWeaponAliasById)dwGetWeaponAliasById;

	typedef void (__thiscall* tGetBulletTypeParameters)(Entity*, int iBulletType, float &fPenetrationPower, float &flPenetrationDistance);
	tGetBulletTypeParameters GetBulletTypeParameters = (tGetBulletTypeParameters)dwGetBulletTypeParameters;

	m_weaponAlias = GetWeaponAliasById(id);

	if(!m_weaponAlias)
		return false;

	char weaponname[128];
	sprintf_s(weaponname,128, "weapon_%s", m_weaponAlias);
	void* SDKweaponInfo = 0;


	DWORD dwLookup = dwLookupWeaponInfoSlot;
	DWORD dwHandle = dwGetFileWeaponInfoFromHandle;

	_asm
	{
		LEA EDX, weaponname
		PUSH EDX
		CALL dwLookup
		ADD ESP, 4
		MOVZX EDI, AX
		PUSH EDI
		CALL dwHandle
		ADD ESP, 4
		MOV SDKweaponInfo, EAX
	}
	
	if(!SDKweaponInfo)
		return false;

	weaponInfo->iBulletType = *(int*)((DWORD)SDKweaponInfo + 0x6c0);
	weaponInfo->iPenetration = *(int*)((DWORD)SDKweaponInfo + 0x884);
	weaponInfo->iDamage = *(int*)((DWORD)SDKweaponInfo + 0x888);
	weaponInfo->flMaxRange= *(float*)((DWORD)SDKweaponInfo + 0x88C);
	weaponInfo->flRangeModifier = *(float*)((DWORD)SDKweaponInfo + 0x890);
	weaponInfo->iBulletsPerShot = *(int*)((DWORD)SDKweaponInfo + 0x894);
	weaponInfo->flCycleTime = *(float*)((DWORD)SDKweaponInfo + 0x898);

	//if (id == WeaponIds::M4A1)
	//{
	//	int specialWeaponMode = *(int*)((DWORD)weapon + 0x9EC);

	//	if (specialWeaponMode == 1)
	//	{
	//		weaponInfo->flRangeModifier = .95f;
	//	}
	//}
	//if (id == WeaponIds::Glock)
	//{
	//	int specialWeaponMode = *(int*)((DWORD)weapon + 0x9EC);

	//	if(specialWeaponMode == 1)
	//	{
	//		int burstShotsRemaining = *(int*)((DWORD)weapon + 0xA2C);

	//		if (burstShotsRemaining > 0)
	//		{
	//			weaponInfo->iDamage = 18;
	//			weaponInfo->flRangeModifier = .9f;
	//		}
	//	}
	//}
	//if (id == WeaponIds::USP)
	//{
	//	int specialWeaponMode = *(int*)((DWORD)weapon + 0x9EC);
	//	if (specialWeaponMode == 1)
	//	{
	//		weaponInfo->iDamage = 30;
	//	}
	//}
	
	int bt = weaponInfo->iBulletType;
	float pp = 0.f;
	float pd = 0.f;
	GetBulletTypeParameters(g_pInterfaces.g_pEntList->GetClientEntity(g_pInterfaces.g_pEngine->GetLocalPlayer()),bt,pp,pd);
	weaponInfo->flPenetrationPower = pp;
	weaponInfo->flPenetrationDistance = pd;

	weaponInfo->pSDKWeaponInfo = (void*)SDKweaponInfo;
	weaponInfo->weaponID = id;
	m_weapon=weaponInfo->weapon = weapon;

	m_bIsWeaponInfoValid = true;
	return true;
}

void TraceLine(SD_Tools::Vector &start, SD_Tools::Vector &end, CTraceFilterSkipTwoEntities* TraceFilter, DWORD dwMask,Ray_t &ray, Trace_t *trace)
{
	ray.Init(start,end);
	g_pInterfaces.g_pEngineTrace->TraceRay(ray,dwMask,TraceFilter,trace);
}

void GetMaterialParameters(int iMaterial, float &flPenetrationModifier, float &flDamageModifier)
{
	switch (iMaterial)
	{
		case CHAR_TEX_METAL : 
			flPenetrationModifier = 0.5f;
			flDamageModifier = 0.3f;
			break;
		case CHAR_TEX_DIRT :
			flPenetrationModifier = 0.5f;
			flDamageModifier = 0.3f;
			break;
		case CHAR_TEX_CONCRETE : 
			flPenetrationModifier = 0.4f;
			flDamageModifier = 0.25f;
			break;
		case CHAR_TEX_GRATE	:
			flPenetrationModifier = 1.0f;
			flDamageModifier = 0.99f;
			break;
		case CHAR_TEX_VENT : 
			flPenetrationModifier = 0.5f;
			flDamageModifier = 0.45f;
			break;
		case CHAR_TEX_TILE :
			flPenetrationModifier = 0.65f;
			flDamageModifier = 0.3f;
			break;
		case CHAR_TEX_COMPUTER : 
			flPenetrationModifier = 0.4f;
			flDamageModifier = 0.45f;
			break;
		case CHAR_TEX_WOOD :
			flPenetrationModifier = 1.0f;
			flDamageModifier = 0.6f;
			break;
		default : 
			flPenetrationModifier = 1.0f;
			flDamageModifier = 0.5f;
			break;
	}

}

inline int xPointContents(const SD_Tools::Vector &vec)
{
	return g_pInterfaces.g_pEngineTrace->GetPointContents(vec,0);
}

bool TraceToExit(SD_Tools::Vector &start, SD_Tools::Vector &dir, SD_Tools::Vector &end, float flStepSize, float flMaxDistance)
{
	float flDistance = 0;
	while (flDistance <= flMaxDistance)
	{
		flDistance += flStepSize;
		
		end.x = start.x + flDistance *dir.x;
		end.y = start.y + flDistance *dir.y;
		end.z = start.z + flDistance *dir.z; 
		if ((xPointContents (end) &0x200400B) == 0)
		{
			return true;
		}
	}

	return false;
}

bool Autowall::isHittable(SD_Tools::Vector &End, Entity* enemy, float &BestDamage)
{
	Entity* m_localEntity = g_pInterfaces.g_pEntList->GetClientEntity(g_pInterfaces.g_pEngine->GetLocalPlayer());
	if(!m_localEntity || *m_localEntity->dormant())
		return false;

	if(GameUtilities::IsVisible(m_localEntity->GetEyePosition(), End, m_localEntity, enemy))
		return true;

	CBaseCombatWeapon* pWeapon = m_localEntity->GetBaseCombatWeapon();
	if(!pWeapon)
		return false;

	GetWeaponInfo(&m_weaponInfo, pWeapon);

	SD_Tools::Vector m_vEyePos = m_localEntity->GetEyePosition();

	int iBulletType = m_weaponInfo.iBulletType;
	int iPenetration = m_weaponInfo.iPenetration;
	float flRangeModifier = m_weaponInfo.flRangeModifier;
	float flPenetrationPower = m_weaponInfo.flPenetrationPower;
	float flPenetrationDistance = m_weaponInfo.flPenetrationDistance;


	SD_Tools::Vector vecDir,penetrationEnd,vecDist,vecStart,vecEnd,vecDirNormalized;
	Trace_t tr;
	Ray_t ray;
	float fCurrentDamage = (float)m_weaponInfo.iDamage;
	float flCurrentDistance = 0.0;
	float flDamageModifier = 0.5;
	float flPenetrationModifier = 1.f;

	float flDistance=0.f;

	
	vecDirNormalized = SD_Tools::MathHelper::SubVector(End, m_vEyePos);
	vecDirNormalized.Normalize();

	vecStart=m_vEyePos;
	int iLoopControl=5;

	while (fCurrentDamage > 0 && iLoopControl > 0)
	{
		iLoopControl--;
		Trace_t tr;


		vecEnd=End;
		vecDir = SD_Tools::MathHelper::SubVector(vecEnd, vecStart);
		flDistance = vecDir.Magnitude();
		vecDir.Normalize();
		TraceLine(vecStart, vecEnd, &CTraceFilterSkipTwoEntities(m_localEntity, enemy), 0x4600400B, ray,&tr);
		if(tr.fraction==1.f)
		{
			TraceLine(vecStart, vecEnd, &CTraceFilterSkipTwoEntities(m_localEntity,0), 0x4600400B, ray,&tr);
			switch(tr.hitgroup)
			{
				case HITGROUP_GENERIC:
					break;
				case HITGROUP_HEAD:
					fCurrentDamage *= 4;
					break;
				case HITGROUP_CHEST:
					fCurrentDamage *= 1.0;
					break;
				case HITGROUP_STOMACH:
					fCurrentDamage *= 1.25;
					break;

				case HITGROUP_LEFTARM:
				case HITGROUP_RIGHTARM:
					fCurrentDamage *= 1.0;
					break;

				case HITGROUP_LEFTLEG:
				case HITGROUP_RIGHTLEG:
					fCurrentDamage *= 0.75;
					break;

			}
			BestDamage = fCurrentDamage;
			return true;
		}
		

		surfacedata_t *pSurfaceData = g_pInterfaces.g_pPhysicAPI->GetSurfaceData(tr.surface.surfaceProps);
		int iEnterMaterial = pSurfaceData->game.material;

		GetMaterialParameters(iEnterMaterial, flPenetrationModifier, flDamageModifier);
		
		bool hitGrate = (tr.contents & CONTENTS_GRATE) != 0;

		if (hitGrate)
		{
			flPenetrationModifier = 1.0f;
			flDamageModifier = 0.99f;
		}

		flCurrentDistance += (tr.fraction * flDistance);
		fCurrentDamage *= pow (flRangeModifier, (flCurrentDistance / 500.f));
		
		if (flCurrentDistance > flPenetrationDistance)
			iLoopControl = 0;
		

		if (!TraceToExit(tr.endpos, vecDirNormalized, penetrationEnd, 24.f,128.f))
		{
			return false;
		}

		Trace_t exitTr;
		TraceLine(penetrationEnd,tr.endpos, 0,MASK_SHOT|CONTENTS_HITBOX, ray, &exitTr);

		if(exitTr.m_pEnt!=tr.m_pEnt)
		{
			TraceLine(penetrationEnd, tr.endpos, &CTraceFilterSkipTwoEntities(exitTr.m_pEnt, 0), MASK_SHOT|CONTENTS_HITBOX, ray,&exitTr);
		}

		pSurfaceData = g_pInterfaces.g_pPhysicAPI->GetSurfaceData(exitTr.surface.surfaceProps);
		int iExitMaterial = pSurfaceData->game.material;

		hitGrate = hitGrate && (exitTr.contents & CONTENTS_GRATE);

		if (iEnterMaterial == iExitMaterial)
		{
			if(iExitMaterial == CHAR_TEX_WOOD ||
				iExitMaterial == CHAR_TEX_METAL)
			{
				flPenetrationModifier *= 2;
			}
		}
		
		vecDist = SD_Tools::MathHelper::SubVector(exitTr.endpos, tr.endpos);

		float flTraceDistance = vecDist.Magnitude();
		
		if (flTraceDistance > (flPenetrationPower * flPenetrationModifier))
		{
			return false;
		}
		flPenetrationPower -= flTraceDistance / flPenetrationModifier;
		flCurrentDistance += flTraceDistance;

		vecStart=exitTr.endpos;
		vecEnd=End;


		fCurrentDamage *= flDamageModifier;

		iPenetration--;
	}
	return false;
}